﻿using System.Diagnostics;
using MultiLang;
namespace Rsdn.Janus
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using AT;
    using BLToolkit.Data;
    using BLToolkit.Data.Linq;
    using JanusLibAing;
    using Properties;
    using SmartApp;

    /// <summary>
    /// Задача отсылки сообщений - AT.
    /// </summary>
    internal class PostMessagesSyncTask : RsdnSyncTask<PostRequest, PostResponse>
    {
#if TRACE_SYNC_TIME
        Stopwatch debugTimer = new Stopwatch();
#endif
        public PostMessagesSyncTask(IServiceProvider provider, string name)
            : base(provider, name, () => Resources.PostMessages)
        {
#if TRACE_SYNC_TIME
            debugTimer.Start();
#endif
        }

        ~PostMessagesSyncTask()
        {
#if TRACE_SYNC_TIME
            debugTimer.Stop();
#endif
        }

        /// <summary>
        /// Determines whether [is task active].
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if [is task active]; otherwise, <c>false</c>.
        /// </returns>
        public override bool IsTaskActive()
        {
			var obMgr = Provider.GetRequiredService<IOutboxManager>();
			return
				obMgr
					.NewMessages
					.FirstOrDefault(msg => !msg.Hold) != null
				|| obMgr.RateMarks.Count > 0;
		}


        /// <summary>
        /// Prepares the request.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        protected override PostRequest PrepareRequest(ISyncContext context)
        {
            bool trace = false;
#if TRACE_SYNC_TIME
            trace = true;
#endif
            using (TimeTracer timeTracer = new TimeTracer("STM:PostMessagesSyncTask.PrepareRequest", trace))
            {
                IOutboxManager outboxManager = Provider.GetRequiredService<IOutboxManager>();

			using (var db = context.CreateDBContext())
                {
                    PostMessageInfo[] messagesForWrite;

                    try
                    {
                        outboxManager.LockMessageEditing();
                        Dictionary<int, bool> lockedMessageIds = outboxManager.GetLockedMessageIds();

                        IQueryable<DataModel.IOutboxMessage> outboxMessages =
                            db.OutboxMessages(
                                m =>
                                !m.Hold
                                //&& ((lockedMessageIds == null) || (!lockedMessageIds.ContainsKey(m.ID)))
                                );

                        Expression<Func<DataModel.IOutboxMessage, PostMessageInfo>> messagesCreator =
                            m =>
                            new PostMessageInfo
                                {
                                    forumId = m.ForumID,
                                    localMessageId = m.ID,
                                    message = Validator.ToSafeString(m.Body)
                                              + (string.IsNullOrEmpty(m.Tagline)
                                                     ? ""
                                                     : "\r\n[tagline]{0}[/tagline]".FormatStr(m.Tagline)),
                                    //MLHIDE
                                    parentId = m.ReplyToID,
                                    subject = m.Subject
                                };


                        IQueryable<PostMessageInfo> postMessageInfos = outboxMessages.Select(messagesCreator);
                        List<PostMessageInfo> messageInfos = postMessageInfos.ToList();
                        if (lockedMessageIds != null)
                        {
                            messageInfos.RemoveAll(m => lockedMessageIds.ContainsKey(m.localMessageId));
                        }

                        messagesForWrite = messageInfos.ToArray();
                    }
                    finally
                    {
                        outboxManager.ReleaseMessageEditing();
                    }

                    Expression<Func<DataModel.IOutboxRate, PostRatingInfo>> expression =
                        r =>
                        new PostRatingInfo
                            {
                                localRatingId = r.ID,
                                messageId = r.MessageID,
                                rate = (int) r.RateType
                            };

                    Table<DataModel.IOutboxRate> outboxRates = db.OutboxRates();
                    PostRatingInfo[] ratesForWrite =
                        outboxRates
                            .Select(expression)
                            .ToArray();

                    IRsdnSyncConfig cfg = GetSyncConfig();
                    PostRequest postRequest =
                        new PostRequest
                            {
                                userName = cfg.Login,
                                password = cfg.Password,
                                writedMessages = messagesForWrite,
                                rates = ratesForWrite
                            };

                    return postRequest;
                }
            }
        }

        /// <summary>
        /// Makes the request.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="svc">The SVC.</param>
        /// <param name="rq">The rq.</param>
        /// <returns></returns>
        protected override PostResponse MakeRequest(ISyncContext context, JanusAT svc, PostRequest rq)
        {
            bool trace = false;
#if TRACE_SYNC_TIME
            trace = true;
#endif
            using (TimeTracer timeTracer = new TimeTracer("STM:PostMessagesSyncTask.MakeRequest", trace))
            {
                svc.PostChange(rq);
                timeTracer.TraceTime("svc.PostChange(rq);");
                context.CheckState();
                timeTracer.TraceTime("context.CheckState();");
                PostResponse response = svc.PostChangeCommit();
                timeTracer.TraceTime("svc.PostChangeCommit();");

                return response;
            }
        }

        /// <summary>
        /// Clears the outbox.
        /// </summary>
        /// <param name="provider">The provider.</param>
        /// <param name="mids">The mids.</param>
        private static void ClearOutbox(IServiceProvider provider, IEnumerable<int> mids)
        {
            int max = provider.MaxInClauseElements();
			using (var db = provider.CreateDBContext())
            {
                foreach (IEnumerable<int> series in mids.SplitToSeries(max))
                {
                    // ReSharper disable AccessToModifiedClosure
                    db.OutboxMessages(m => series.Contains(m.ID)).Delete();
                    // ReSharper restore AccessToModifiedClosure
                }
            }
        }

        /// <summary>
        /// Clears the rates outbox.
        /// </summary>
        /// <param name="provider">The provider.</param>
        private static void ClearRatesOutbox(IServiceProvider provider)
        {
			using (var db = provider.CreateDBContext())
            {
                db
                    .OutboxRates()
                    .Delete();
            }
        }

        /// <summary>
        /// Processes the response.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="request">The request.</param>
        /// <param name="response">The response.</param>
        /// <exception cref="Exception"><c>Exception</c>.</exception>
        protected override void ProcessResponse(
            ISyncContext context,
            PostRequest request,
            PostResponse response)
        {
            bool trace = false;

#if TRACE_SYNC_TIME
            trace = true;
#endif
            using (TimeTracer timeTracer = new TimeTracer("STM:PostMessagesSyncTask.MakeRequest", trace))
            {
                ClearOutbox(context, response.commitedIds);
                ClearRatesOutbox(context);

                context.StatisticsContainer.AddValue(JanusATInfo.OutboundMessagesStats, response.commitedIds.Length);
                context.StatisticsContainer.AddValue(
                    JanusATInfo.FailedOutboundMessagesStats, response.exceptions.Length);
                context.StatisticsContainer.AddValue(JanusATInfo.OutboundRatesStats, request.rates.Length);

                Exception syncException = null;

                foreach (PostExceptionInfo ex in response.exceptions)
                {
                    if (syncException == null)
                    {
                        syncException = new SyncTroubleException();
                    }

                    SyncErrorInfo syncErrorInfo = new SyncErrorInfo(
                        SyncErrorType.CriticalError, GetDisplayName(), ex.exception);
                    syncException.Data.Add(syncErrorInfo, ex);
                    context.TryAddSyncError(syncErrorInfo);

                    // hack for exception explanation
                    if (ex.exception.Contains("'Мусор'")) //MLHIDE
                    {
                        string comment =
                            ml.ml_string(11, "Форум 'Мусор' означает удаленные сообщения.") +
                            ml.ml_string(12, " Возможно вы пытаетесь отправить на ответ на удаленное сообщение");

                        SyncErrorInfo syncErrorInfoAdd = new SyncErrorInfo(
                            SyncErrorType.Warning, ml.ml_string(13, "Пояснение"), comment);
                        context.TryAddSyncError(syncErrorInfoAdd);
                    }
                }

                if (syncException != null)
                {
                    throw syncException;
                }


                // TODO: Нужна какая то механика, отображающая исключения для каждого сообщения в UI исходящих. AN: already did?
            }
        }
    }
}
